namespace Profit.Services
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Xml.Linq;
    using Core;
    using Core.Contracts.Interfaces;
    using Properties;

    /// <summary>
    /// Application repository
    /// </summary>
    public sealed class Repository
    {
        private static readonly string FILEPATH_AGENTS = Settings.Default.PathAgents +  "\\agents.xml";
        private static readonly string FILEPATH_FITTINGS = Settings.Default.PathFittings +  "\\fittings.xml";

        private static readonly ObservableCollection<IAgent> _agents = new ObservableCollection<IAgent>();
        private static ObservableCollection<IFitting> _fittings = null;

        private static readonly IRepository _default = new DefaultRepository(Settings.Default.CacheDuration);

        /// <summary>
        /// Obtains the default single instance repository
        /// </summary>
        public static IRepository Default
        {
            get { return _default; }
        }
        /// <summary>
        /// Constructor
        /// </summary>
        static Repository()
        {
            if(File.Exists(FILEPATH_AGENTS))
                Task.Factory.StartNew(
                    () => _agents.Load(FILEPATH_AGENTS), 
                    CancellationToken.None,
                    TaskCreationOptions.None, 
                    TaskScheduler.FromCurrentSynchronizationContext());
        }
        /// <summary>
        /// Obtains the Agents list
        /// </summary>
        public static ObservableCollection<IAgent> Agents
        {
            get{ return _agents; }
        }
        /// <summary>
        /// Obtains the fittings list
        /// </summary>
        public static ObservableCollection<IFitting> Fittings
        {
            get { return _fittings ?? (_fittings = new ObservableCollection<IFitting>(EnumerateFittings())); }
        }
        /// <summary>
        /// Enumerates fittings
        /// </summary>
        /// <param name="predicateName">A optional predicate used to filter fittings on name</param>
        /// <param name="predicateRobot">An optional predicate used to filter on robot name</param>
        /// <returns>An IEnumerable of IFitting</returns>
        public static IEnumerable<IFitting> EnumerateFittings(Predicate<string> predicateName = null, Predicate<string> predicateRobot = null)
        {
            if(File.Exists(FILEPATH_FITTINGS))
                using (var reader = new StreamReader(FILEPATH_FITTINGS, Encoding.UTF8))
                {
                    var elements = XElement.Load(reader);

                    foreach (var element in elements.Elements("fitting"))
                    {
                        var currentFitting = element;

                        if (!currentFitting.HasAttributes || 
                            currentFitting.Attribute("name") == null ||
                            string.IsNullOrEmpty(currentFitting.Attribute("name").Value))
                        continue;

                        if (predicateName != null && !predicateName(currentFitting.Attribute("name").Value)) 
                            continue;

                        var robotElement = currentFitting.Elements("robot").FirstOrDefault();
                        if (robotElement == null || 
                            !robotElement.HasAttributes || 
                            robotElement.Attribute("name") == null ||
                            string.IsNullOrEmpty(robotElement.Attribute("name").Value)) 
                        continue;

                        if(predicateRobot!=null && !predicateRobot(robotElement.Attribute("name").Value))
                            continue;

                        var bot = Default.LoadByName<IRobot>(robotElement.Attribute("name").Value);
                        if (bot == null) 
                            continue;

                        var fitting = new Fitting(currentFitting.Attribute("name").Value, bot);
                        if (fitting.Robot == null) 
                            continue;

                        fitting.IsNotifying = false;
                        fitting.Robot.IsNotifying = false;

                        foreach (var el in robotElement.Elements("head"))
                            fitting.Robot.AddModule(GetModule(el.Value), false);

                        foreach (var el in robotElement.Elements("chassis"))
                            fitting.Robot.AddModule(GetModule(el.Value), false);

                        foreach (var el in robotElement.Elements("legs"))
                            fitting.Robot.AddModule(GetModule(el.Value), false);

                        fitting.IsNotifying = true;
                        fitting.Robot.IsNotifying = true;

                        yield return fitting;
                    }
                    reader.Close();
                }
        }
        /// <summary>
        /// Parse the content of the xmlelement and returns an instance of IModule or null
        /// </summary>
        /// <param name="value">The xmlelement content</param>
        /// <returns>A new instance of IModule or null</returns>
        internal static IModule GetModule(string value)
        {
            if (string.IsNullOrEmpty(value)) return null;
            var parts = value.Split(',');
            if (parts == null || parts.Length == 0) return null;

            IModule module = null;
            if (!Default.Exists(parts[0], out module)) return null;

            module.IsNotifying = false;

            module.LoadUsableAmmunitions();

            IAmmunition ammo = null;
            module.Ammunitions = parts.Length > 1 && Default.Exists(parts[1].Trim(), out ammo)
                ? ammo
                : null;

            module.IsNotifying = true;
            return module;
        }
        /// <summary>
        /// Saves agent list to disk
        /// </summary>
        public static void SaveAgents()
        {
            Task.Factory.StartNew(
                ()=> Agents.Save(FILEPATH_AGENTS),
                CancellationToken.None,
                TaskCreationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
        }
        /// <summary>
        /// Saves fittings list to disk
        /// </summary>
        public static void SaveFittings(bool reload)
        {
            if(_fittings ==null) return;

            Task.Factory.StartNew(
                () =>
                    {
                        _fittings.Save(FILEPATH_FITTINGS);

                        if (reload)
                        {
                            _fittings.Clear();
                            _fittings.Load(FILEPATH_FITTINGS);
                        }
                    },
                CancellationToken.None,
                TaskCreationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
        }
    }
}